
	/***********************************************************************************************

		@API
		Class: OptionMap

		Methods:
			map			OptionMap			()

			map			option				(id, data/setter*)
			map			options				(omap, options*)

			data		optionData		(id*)
			b				hasOption			(id, value*)

			v				get						(id, old*)
			map			set						(id/options, value*)
			map			unset					(id)
			obj			options				(old*)

		Legend:
			data		{ dvalue*, validator*, setter*, getter*, exposed*, id*, name*, parent* }
			setter	f(v, oldv, id)
			getter	f(v, oldv, id)

	************************************************************************************************/

	//-------------------------------------------------------------------------------
	jsl.Class("jsl.OptionMap", {

		//Private vars
		//-------------------------------------------------------------------------------
		__os: null,

		//-------------------------------------------------------------------------------
		OptionMap: function() {//!
			if(arguments.length) throw jsl.ParamError()//!
			this.__os = []
		},
		//-------------------------------------------------------------------------------
		option: function(id, data) {
			//@sys:	Firefox - setter and getter var names -> syntax error (also in jslint). No error for property names
			var ix, name, pid, pdata, value, vld, setterM, getterM, exposed, odata, vid, nid
			//!
			if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
			jsl.validate(id, "qid", "id")//!

			//an option can be overwritten
			ix = id.lastIndexOf(".")
			if(ix !== -1) {

        name  = id.slice(ix + 1)
				pid		= id.slice(0, ix)
				pdata	= this["__o_" + pid]

				//!
        if(!pdata)												throw jsl.ParamError("NotDefined:id(parent)")
        if(pdata.map !== this)						throw jsl.ParamError("OperationDenied")
        if(!jsl.isObject(this.get(pid)))	throw jsl.ParamError("BadType:id(parent):object")//!
			}
      else
        name 	= id

			//1 - no data
			if(arguments.length === 1)
				value = {}

			//2 - function
			else if(jsl.isFunction(data)) {
				value		= null
				setterM	= data
			}
			//3 - data
			else {
				//!
				if(!jsl.isObject(data))	throw jsl.ParamError("BadType:data:object | function ")//!

				//@sys: jscoverage throw a syntax error if setter is write as property
				value		= "dvalue" in data ? data.dvalue : null
				setterM	= data["setter"]
				getterM	= data["getter"]
				vld			= data.validator

				//!
				if(setterM 	!= null)	jsl.validate(setterM, "f", 	"data.setter")
				if(getterM 	!= null)	jsl.validate(getterM, "f", 	"data.getter")
				if(vld 			!= null)	jsl.validate(vld, 		"s+", "data.validator")

				if(vld) {
					vid = vld.match(/^([^(\[\/]+)/)
					vid = vid && vid[1]
					if(!jsl.__vrules[vid])	throw jsl.ParamError("NotDefined:data.validator")
				}//!

				if(data.exposed != null) {

					exposed = data.exposed//!
					if(!jsl.isBoolean(exposed)) throw jsl.ParamError("BadType:data.exposed:boolean")//!

					if(!!+exposed) {

						nid = id.replace(/\.(.)/g, function(s, g1) { return g1.toUpperCase() })
						this[nid] = function(value) {
							var v//!
							if(arguments.length > 1)	throw jsl.ParamError()//!

							v = this
							if(!arguments.length)  v = this.get(id)
							else                   this.set(id, value)
							return v
						}
					}
				}
			}

			odata = {
								id:					id 		+ "" 			,
		            name:				name  + "" 			,
		            parent:			pdata		|| null ,
								setter:	 		setterM	|| null	,
								getter:			getterM	|| null ,
								validator:	vld			|| null ,
								dvalue:			value						,
								exposed:		!!+exposed,
								childs:			[],
								map:				this
							}

			this["__ov_" 	+ id]	= odata.dvalue
			this["__oov_" + id]	= odata.dvalue
			this["__o_" 	+ id] = odata

			this.__os.push(odata)
			if(pid) pdata.childs.push(odata)

			return this
	  },
		//-------------------------------------------------------------------------------
		optionData: function(id) {
			var dt, map, ls, a, l, ids//!
			if(arguments.length > 1)	throw jsl.ParamError()//!

			//1-single option
			if(arguments.length) {//!
				jsl.validate(id, "qid", "id")
				if(!this["__o_" + id])	throw jsl.ParamError("NotDefined:id")//!
				ls = [ this["__o_" + id] ]
			}

			//2-all options
			else {
				map	= this
				ls 	= []
				ids = {}

				while(map) {
					if(map.__os) {
						dt = map.__os
						for(a = 0, l = dt.length; a < l; a++)
							if(!ids[dt[a].id]) {
								ls.push(dt[a])
								ids[dt[a].id] = true
							}
					}
					map	= map.__proto
				}
			}

			for(a = 0, l = ls.length; a < l; a++) {
				dt = ls[a]
				ls[a] = {
           id:    	  		dt.id     	,
           name:   		 		dt.name   	,
           validator:			dt.validator,
           setter: 			 	dt.setter 	,
           getter: 				dt.getter 	,
           exposed:				dt.exposed	,
           dvalue:				dt.dvalue		,
					 parent:				dt.parent ? dt.parent.id : null
         }
			}

			return arguments.length ? ls[0] : ls.sort(function(o1, o2) { return o1.id <= o2.id ? -1 : 1 })
	  },
		//-------------------------------------------------------------------------------
		hasOption: function(id, value) {
			var b//!
			if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
			jsl.validate(id, "qid", "id")
			if(arguments.length === 2)	jsl.validate(value, "b", "value")//!

			b = !!this["__o_" + id]
			if(b && !!+value) b = this.hasOwnProperty("__ov_" + id)
			return b
		},
		//-------------------------------------------------------------------------------
		get: function(id, old) {
  		var dt, v, ov, b, cs
			//!
			if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
			jsl.validate(id, "qid", "id")
			if(arguments.length === 2)	jsl.validate(old, "b", "old")//!

			dt = this["__o_" + id]//!
			if(!dt)	throw jsl.ParamError("NotDefined:id")//!

			v 	= this["__ov_"  + id]
			ov 	= this["__oov_" + id]
  		b		= !!+old

  		if(b)
  			v = ov

 			else if(dt.getter)
				try 			{ v = dt.getter.call(this, v, ov, id) }
				catch(e)	{ throw jsl.ScriptError("BadScript:getter", e)	}

			cs = dt.childs//!
      if(cs.length && !jsl.isObject(v)) throw jsl.MisuseError("BadReturnType:getter:object")//!

			for(var a = 0, l = cs.length; a < l; a++)
				v[cs[a].name] = this.get(cs[a].id, b)

			return v
	  },
		//-------------------------------------------------------------------------------
		set: function(id, value) {
  		var ov, v, cdt, dt, pid
			//!
			if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
			if(jsl.isString(id)) 	jsl.validate(id, "qid", "id")
			else									jsl.validate(id, "o", 	"options")//!

			//1-options
			if(typeof id !== "string") {//!
				if(arguments.length !== 1)	throw jsl.ParamError("NotRequired:value")//!
				for(pid in id) {
					dt = this["__o_" + pid]
					if(dt) this.set(dt.id, id[pid])
				}
			}
  		//2-id
			else {
				dt = this["__o_" + id]//!
				if(!dt)						throw jsl.ParamError("NotDefined:id")
				if(dt.validator)	jsl.validate(value, dt.validator, "value")//!

				v 	= value
				ov 	= this["__oov_" + id]

				if(dt.setter)
					try				{ v = dt.setter.call(this, v, ov, id) }
					catch(e)	{ throw jsl.ScriptError("BadScript:setter", e)	}

				//!
	      if(dt.childs.length && !jsl.isObject(v)) throw jsl.MisuseError("BadReturnType:setter:object")//!

				this["__oov_" + id] = this["__ov_" + id]

	      if(typeof v !== "object" || v === null) this["__ov_" 	+ id] = v
	      else if(v instanceof String)						this["__ov_" 	+ id] = v + ""
	      else if(v instanceof Number)						this["__ov_" 	+ id] = +v
	      else if(v instanceof Boolean)						this["__ov_" 	+ id] = !!+v
	      else 																		this["__ov_" 	+ id] = v

				for(var a = 0, l = dt.childs.length; a < l; a++) {
					cdt = dt.childs[a]
					if(cdt.name in v) this.set(cdt.id, v[cdt.name])
				}
			}

			return this
		},
		//-------------------------------------------------------------------------------
		unset: function(id) {
			var dt, v//!
			if(arguments.length !== 1)	throw jsl.ParamError()
			jsl.validate(id, "qid", "id")
			if(!this["__o_" + id])	throw jsl.ParamError("NotDefined:id")//!

			dt 	= this["__o_" + id]
			v		= this["__ov_" + id]

			if(dt.map === this)	this["__ov_" + id] = dt.dvalue
			else								delete this["__ov_" + id]

			this["__oov_" + id] = v
			return this
	  },
		//-------------------------------------------------------------------------------
		options: function(old) {
  		var os, v, map, b, a, l, id, i, omap, options

  		//1-options reading
  		if(!arguments.length || jsl.isBoolean(old)) {//!
  			if(arguments.length > 1)	throw jsl.ParamError()//!

				os	= this.__os
				map	= this.__proto
				v		= {}

				while(map) {
					if(map.__os) os = os.concat(map.__os)
					map	= map.__proto
				}

	  		b 	= !!+old
	  		map = {}

				for(a = 0, l = os.length; a < l; a++) {

					id = os[a].id
					//the sub-options are automatically returned in parent options
					if(os[a].name === id && !map[id]) {
						v[id] 	= this.get(id, b)
						map[id] = true
					}
				}
  		}

  		//2-options import
  		else {

  			omap = old
  			options = arguments[1]

  			//!
				if(arguments.length > 2)	throw jsl.ParamError()
				jsl.validate(omap, "o", "omap")
				if(!jsl.OptionMap.isClassOf(omap))	throw jsl.ParamError("BadType:omap:jsl.OptionMap")
				if(omap === this)										throw jsl.ParamError("BadValue:omap:omap = this")
				if(arguments.length === 2)					jsl.validate(options, "a", "options")//!

				//we must get first the parents and then the childs, ex: ["A.a", "A"] must be sorted in ["A", "A.a"]
				if(arguments.length === 1) 	os = omap.optionData()
				else												os = options.slice().sort()

				for(a = 0, l = os.length; a < l; a++) {

					id = os[a].id || os[a]
					//!
					jsl.validate(id, "qid", "options[" + a + "]")
					if(!omap["__o_" + id])	throw jsl.ParamError("NotDefined:omap(option=" + id + ")")//!

					i = id.lastIndexOf(".")
					//!
					if(i !== -1 && !this["__o_" + id.slice(0, i)])								throw jsl.ParamError("NotDefined:id(parent)")
					if(i !== -1 && !jsl.isObject(this["__ov_" + id.slice(0, i)]))	throw jsl.ParamError("OperationDenied:options[" + a + "]")//!

					if(i !== -1)	this["__o_" + id.slice(0, i)].map.option(id, omap["__o_" + id])
					else 					this.option(id, omap["__o_" + id])
				}

				return this
  		}

      return v
	  }
	})
